main-logo

技術登攀録

一覧へ

gqlgenとgormでDB操作の依存性注入(DI)を行う

[golang]

更新日:10/20/2018


こんにちは!onc-limb です。 仕事では TypeScript のフレームワークである NestJS を使用しています。

NestJS では依存性注入を@Module デコレーターを使ってモジュールごとに行いますが、go 言語ではどのように実装すれば良いかわからなかったので試してみました。

背景:

開発言語  go 言語

フレームワーク  gqlgen、gorm

依存性注入はなぜ必要なのかは別の記事で記述して、今回は具体的な手法を記述します。

今回やりたいこと

  • DB 操作は repository からのみ行う。
  • domain に repository のインターフェースのみ定義して、usecase は domain を参照する。
  • repository は domain に定義したインターフェースを継承して実装する。

結論

repository, usecase に New 関数を定義して、特定のインスタンスをフィールドに継承したインスタンスを作成することで解決した。

Repository の作成

package repository

type Repository struct {
	DB *gorm.DB
}

func NewRepository(db *gorm.DB) *Repository {
	return &Repository{DB: db}
}

usecase の作成

package usecase

type Usecase struct {
	Repo domain.Repository
}

func NewUsecase(repo domain.Repository) *Usecase {
	return &Usecase{Repo: repo}
}

domain にインターフェースの作成

package domain

type Repository interface {
	// DBに接続する関数の型を定義する
}

gqlgen が自動生成している Resolver 型に Repository フィールドを追加

package graph

type Resolver struct {
	*gorm.DB
	Repository domain.Repository // 追加
}

main 関数で DB の接続情報を持った Repository インスタンスを Resolver に注入する。

package main

func main() {
	// 無関係のコードは割愛
	db, _ := gorm.Open(postgres.Open(dsn), &gorm.Config{}) // dsnは事前に定義
	repo := infra.NewRepository(db)

	// graphqlのresolverにrepository設定
	graphqlHandler := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{Repository: repo}}))

	// 以下で起動処理

あとは今後作成する usecase や repository の関数のレシーバーに Usecase 型や Repository 型を指定してやれば、レシーバーから DI した DB インスタンスや Repository インスタンスにアクセスすることができる。

package usecase

func (u *Usecase) Get(id uint) (data, error) {
	retrun u.Repository.Find(id)
}
package repository

func (r *Repository) Find(id uint) (data, error) {
	result := r.DB.First(&data, id)
	if result.Error != nil {
		panic()
	}
	return data, nil
}

この方法が本当に良いのか、一般的なのかはわかりませんが、ソースコード上 usecase や resolver が repository に依存することがなくなったので今回はよしとします。

go 言語は仕様で package の循環参照が禁止されているので、依存性違反はできないようになっていますね。

個人開発のような小規模なアプリなら package を一つにまとめちゃったほうが早いし楽なのかもしれません。(そもそも小規模アプリに go 言語は採用しないかもしれませんが…)